package ags.muse.movement;

import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Random;

import ags.muse.base.Rules;
import ags.util.points.*;

public class MovementPoly {
	private ArrayList<AbsolutePoint> pointlist;
	private Polygon awt_poly;
	private Rectangle2D bounds;
	
	public boolean contains(Point2D p) {
		return awt_poly.contains(p);
	}
	
	public MovementPoly(Rules rules, AbsolutePoint location, RelativePoint velocity, int direction, int ticks) {
		// Set up the movement polygon
		Rectangle2D.Double mapbox = new Rectangle2D.Double(18-0.001, 18-0.001, rules.BATTLEFIELD_WIDTH - 36 + 0.002, rules.BATTLEFIELD_HEIGHT - 36 + 0.002);
		pointlist = MovementPoly.getMovementPoly(rules, location, velocity, mapbox, direction, 1, ticks);
		Collections.reverse(pointlist);
		pointlist.addAll(MovementPoly.getMovementPoly(rules, location, velocity, mapbox, direction, -1, ticks));
		
		// Create a awt polygon for painting
        awt_poly = new Polygon();
        for (AbsolutePoint p : pointlist) {
          awt_poly.addPoint((int)(p.x+0.5), (int)(p.y+0.5));
        }
        bounds = awt_poly.getBounds2D();
        if (bounds.getHeight() == 0) {
        	bounds.setRect(bounds.getMinX(), bounds.getMinY()-1, bounds.getWidth(), 1);
        }
        if (bounds.getHeight() == 0) {
        	bounds.setRect(bounds.getMinX()-1, bounds.getMinY(), 1, bounds.getHeight());
        }
	}
	
	public void paint(Graphics2D g) {
		if (awt_poly.npoints != 0) {
		  g.fillPolygon(awt_poly);
		}
	}
	
	public AbsolutePoint getRandomPoint() {
		Random rand = new Random();
    	AbsolutePoint p;
    	int tries = 0;
    	do {
    		if (tries > 20) {
    			return null;
    		}
    		p = AbsolutePoint.fromXY(bounds.getMinX() + rand.nextDouble()*bounds.getWidth(), bounds.getMinY() + rand.nextDouble()*bounds.getHeight());
    		tries++;
    	} while (!awt_poly.contains(p));
    	return p;
	}

	private static double[][] getVelocityList(Rules rules, double vel, int dir, int turndir, int size) {
		double[][] list = new double[size][];

		// Flip negative direction, to make things simpler
		vel *= dir;

		// Iterate through acceleration
		for (int i=0; i<size; i++) {
			if (vel < 0) {
				vel += rules.DECELERATION;
			} else {
				vel += rules.ACCELERATION;
			}

			if (vel >= rules.MAX_VELOCITY) {
				// Optimization for once max velocity is achieved
				double[] pair = new double[] {rules.MAX_VELOCITY * dir, rules.getTurnRate(rules.MAX_VELOCITY) * turndir};
				Arrays.fill(list, i, size, pair);
				break;
			}

			// Flip velocity again to work with the desired direction
			list[i] = new double[] {vel * dir, rules.getTurnRate(vel) * turndir};
		}

		return list;
	}

	private static ArrayList<AbsolutePoint> getMovementPoly(Rules rules, AbsolutePoint position, RelativePoint velocity, Rectangle2D.Double mapbox, int dir, int turndir, int ticks) {
		ArrayList<AbsolutePoint> poly_list = new ArrayList<AbsolutePoint>();
		
		double[][] velocity_list = getVelocityList(rules, velocity.magnitude, dir, turndir, ticks*2);

		AbsolutePoint cursor = position;
		RelativePoint vcursor = velocity;
		AbsolutePoint pivot_point = position;
		int pivot_index = 0;
		int index = 0;

		// Extend n' bend
		while (pivot_index <= index) {
			if (!mapbox.contains(cursor) && (cursor == position)) {
			  System.out.println("BROKEN");
			  System.out.println(position.x + ", " + position.y);
			  System.out.println(mapbox.toString());
			}
			while (!mapbox.contains(cursor) && index > 0) {
				// Move path backwards
				index--;
				vcursor = RelativePoint.fromDM(vcursor.direction, -vcursor.magnitude);
				cursor = cursor.addRelativePoint(vcursor);
				double direction = vcursor.direction;
				if (pivot_index >= index) {
					direction -= velocity_list[pivot_index][1];
					pivot_index = index;
				}
				vcursor = RelativePoint.fromDM(direction , velocity_list[index][0]);
			}

			AbsolutePoint last_cursor = cursor;
			RelativePoint last_vcursor = vcursor;
			while (ticks > index && mapbox.contains(cursor)) {
				last_vcursor = vcursor;
				last_cursor = cursor;
				vcursor = RelativePoint.fromDM(vcursor.direction, velocity_list[index][0]);
				cursor = cursor.addRelativePoint(vcursor);
				index++;
			}

			if (!mapbox.contains(cursor) && index > 0) {
				vcursor = last_vcursor;
				cursor = last_cursor;
				index--;
			}
			
			if (!mapbox.contains(cursor)) {
				break;
			}

			poly_list.add(cursor);

			if (pivot_index == index) {
				break;
			}

			// Pivot at pivot_point while straightening out the path
			RelativePoint r = RelativePoint.fromPP(pivot_point, cursor);
			r = RelativePoint.fromDM(r.direction + velocity_list[pivot_index][1], r.magnitude);
			cursor = pivot_point.addRelativePoint(r);
			vcursor = RelativePoint.fromDM(vcursor.direction + velocity_list[pivot_index][1], vcursor.magnitude);

			// Increment pivot_point
			r = RelativePoint.fromDM(vcursor.direction, velocity_list[pivot_index][0]);
			pivot_point = pivot_point.addRelativePoint(r);
			pivot_index++;
		}

		while (index > 0) {
			// Move path backwards
			index--;
			vcursor = RelativePoint.fromDM(vcursor.direction, -vcursor.magnitude);
			cursor = cursor.addRelativePoint(vcursor);
			double direction = vcursor.direction;
			if (pivot_index >= index) {
				direction -= velocity_list[pivot_index][1];
				pivot_index = index;
			}
			vcursor = RelativePoint.fromDM(direction , velocity_list[index][0]);

			poly_list.add(cursor);
		}

		return poly_list;
	}
}
